/*
 * DictionaryWorker.java
 *
 * Copyright(c) 2010
 * Michael Kroez
 * kroez@studiorutton.de
 *
 * All rights reserved.
 *
 * $Id$
 * 
 */

package de.studiorutton.gerengdic.dictionary;

import java.text.ParseException;
import java.util.concurrent.atomic.AtomicBoolean;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import de.studiorutton.access.FileAccess;
import de.studiorutton.access.FileAccessException;
import de.studiorutton.access.FileLocation;
import de.studiorutton.gerengdic.databaseaccess.DatabaseAccess;
import de.studiorutton.gerengdic.databaseaccess.DatabaseAccessException;
import de.studiorutton.gerengdic.databaseaccess.ContentAccess.RecordAccess.RecordIterator;
import de.studiorutton.gerengdic.dictionary.DictionaryNotification.ErrorCode;
import de.studiorutton.gerengdic.dictionary.records.LeftRightValuesRecord;
import de.studiorutton.tre.AMatchResult;
import de.studiorutton.tre.SimpleTre;
import de.studiorutton.tre.TreException;

public class DictionaryWorker {
	private final static String TAG = "learningwords";
	private final FileLocation fileLocation;
	private HandlerThread searchThread;
	SearchMessageHandler handler;
	private DatabaseAccess dba;
	private SimpleTre simpleTre;
	private AtomicBoolean isRunning;

	public static final int DONE_FULL = 0;
	public static final int DONE_CANCELLED = 1;
	public static final int DONE_ERROR = -1;

	private static final int SEARCH_CANCEL = 0;
	private static final int SEARCH_NTH_RECORD = 1;
	private static final int SEARCH_FIRSTN_RECORDS = 2;
	private static final int SEARCH_MATCHING_RECORDS = 3;

	private static final String REGEX_KEY = "regex";
	private static final String MATCH_FULL_WORD_KEY = "matchfullword";
	private static final String MATCH_MODE_KEY = "matchmode";
	private static final String AMATCH_ENABLED = "amatch_enabled";
	private static final String MAX_RESULTS = "maxresults";

	public DictionaryWorker(FileLocation fileLocation)
			throws FileAccessException, DatabaseAccessException {

		this.fileLocation = fileLocation;
		FileAccess.checkExistsAndReadable(fileLocation);

		isRunning = new AtomicBoolean(false);
		startSearchThread();
	}

	public boolean getIsRunning() {
		return isRunning.get();
	}

	public DatabaseAccess getDatabaseAccess() {
		return dba;
	}

	public void startSearchThread() throws DatabaseAccessException,
			FileAccessException {
		dba = new DatabaseAccess(fileLocation.createFilePath());

		if (searchThread == null) {
			searchThread = new HandlerThread("Dictionary Thread");
			searchThread.start();
			Looper looper = searchThread.getLooper();
			handler = new SearchMessageHandler(looper);
		}
	}

	public void stopSearchThread() {
		if (searchThread != null)
			searchThread.interrupt();
		searchThread = null;
	}

	public void cancelSearchOperation() {
		Message msg = Message.obtain();
		msg.what = SEARCH_CANCEL;
		handler.sendMessage(msg);
	}

	class SearchMessageHandler extends Handler {
		public SearchMessageHandler(Looper looper) {
			super(looper);
		}

		protected void finalize() {

			// Log.d(TAG, "stopping SearchMessageHandler");
		}

		public void handleMessage(Message msg) {
			// Log.d(TAG,
			// "handleMessage in worker thread called with msg.what = "
			// + msg.what + ".");
			// Do something with the message
			if (msg.what == SEARCH_CANCEL)
				return;
			IRecordsUpdateListener rul = (IRecordsUpdateListener) msg.obj;
			DatabaseAccess dba = getDatabaseAccess();

			isRunning.set(true);
			if (rul != null)
				rul.notifyStart();

			int result = 0;
			switch (msg.what) {
			case SEARCH_NTH_RECORD:
				// Debug.startMethodTracing("learningwords-nth");
				result = executeSearchNthRecord(msg);
				// Debug.stopMethodTracing();
				break;
			case SEARCH_FIRSTN_RECORDS:
				// Debug.startMethodTracing("learningwords-firstn");
				result = executeSearchFirstNRecords(msg);
				// Debug.stopMethodTracing();
				break;
			case SEARCH_MATCHING_RECORDS:
				// Debug.startMethodTracing("learningwords-smr");
				result = executeSearchMatchingRecords(msg);
				// Debug.stopMethodTracing();
				break;
			}
			isRunning.set(false);
			if (rul != null)
				rul.notifyDone(result);
			return;
		}

		protected int executeSearchNthRecord(Message msg) {
			IRecordsUpdateListener rul = (IRecordsUpdateListener) msg.obj;
			int nth = msg.arg1;

			int result = DONE_FULL;
			RecordIterator ri = null;
			try {
				ri = dba.getContentAccess().getRecordAccess().getIterator();
			} catch (DatabaseAccessException e) {
				notifyError(rul, e.getLocalizedMessage());
			}
			int crrLine = 0;

			while (crrLine < (nth - 1) && ri.hasNext()
					&& !hasMessages(SEARCH_CANCEL)) {
				try {
					ri.next();
				} catch (DatabaseAccessException e) {
					notifyError(rul, e.getLocalizedMessage());
					result = DONE_ERROR;
				}
				crrLine++;
			}
			if (hasMessages(SEARCH_CANCEL)) {
				result = DONE_CANCELLED;
				return result;
			}

			LeftRightValuesRecord record = null;
			try {
				record = LeftRightValuesRecord.parseDictionaryLine(ri.next());
			} catch (ParseException e) {
				notifyError(rul, new DictionaryNotification(
						ErrorCode.ERROR_CANNOT_PARSE_RECORD, new Integer(
								crrLine + 1).toString()).getLocalizedMessage());
				result = DONE_ERROR;
			} catch (DatabaseAccessException e) {
				notifyError(rul, e.getLocalizedMessage());
				result = DONE_ERROR;
			}
			if (record == null) {
				notifyError(rul, new DictionaryNotification(
						ErrorCode.ERROR_UNEXPECTED_END_OF_FILE, Integer.valueOf(
								crrLine + 1).toString()).getLocalizedMessage());

				result = DONE_ERROR;
			}
			if (rul != null)
				rul.notifyNewRecord(record);

			return result;
		}

		protected int executeSearchFirstNRecords(Message msg) {
			IRecordsUpdateListener rul = (IRecordsUpdateListener) msg.obj;

			int result = DONE_FULL;
			int lastLine = msg.arg1;
			int crrLine = 0;
			RecordIterator ri = null;
			try {
				ri = dba.getContentAccess().getRecordAccess().getIterator();
			} catch (DatabaseAccessException e) {
				notifyError(rul, e.getLocalizedMessage());
			}
			while (ri.hasNext() && !hasMessages(SEARCH_CANCEL)
					&& crrLine <= (lastLine - 1)) {
				if (hasMessages(SEARCH_CANCEL)) {
					result = DONE_CANCELLED;
					break;
				}
				LeftRightValuesRecord record;
				try {
					record = LeftRightValuesRecord.parseDictionaryLine(ri
							.next());
				} catch (Exception e) {
					notifyError(rul, e.getLocalizedMessage());
					result = DONE_ERROR;
					break;
				}
				crrLine++;
				if (record == null) {
					notifyError(rul,
							new DictionaryNotification(
									ErrorCode.ERROR_UNEXPECTED_END_OF_FILE,
									Integer.valueOf(crrLine + 1).toString())
									.getLocalizedMessage());
					result = DONE_ERROR;
					break;
				}
				if (rul != null) {
					rul.notifyNewRecord(record);
				}
			}
			return result;
		}

		protected int executeSearchMatchingRecords(Message msg) {
			IRecordsUpdateListener rul = (IRecordsUpdateListener) msg.obj;

			simpleTre = new SimpleTre();
			Bundle bundle = msg.getData();
			String regex = bundle.getString(REGEX_KEY);
			int matchFullWordMode = bundle.getInt(MATCH_FULL_WORD_KEY);
			int language = bundle.getInt(MATCH_MODE_KEY);
			boolean amatchEnabled = bundle.getBoolean(AMATCH_ENABLED);
			int maxResults = bundle.getInt(MAX_RESULTS);

			try {
				simpleTre.multiCompileRegex(regex, matchFullWordMode, false,
						true, amatchEnabled);
			} catch (TreException e) {
				rul.notifyError(e.getLocalizedMessage());
			}

			int result = DONE_FULL;
			String line;
			int resultNum = 0;
			try {
				RecordIterator ri = dba.getContentAccess().getRecordAccess()
						.getIterator();

				int lineCount = 0;
				while (ri.hasNext() && !hasMessages(SEARCH_CANCEL)
						&& (resultNum <= maxResults)) {
					line = ri.next();
					AMatchResult[] leftRightResults = { new AMatchResult(),
							new AMatchResult() };
					if (simpleTre.matchAllRegex(line, language,
							leftRightResults) == SimpleTre.MATCH_FOUND) {
						LeftRightValuesRecord record = LeftRightValuesRecord
								.parseDictionaryLine(line);
						// Log.d( TAG, "Line: " + line + " ResultLeft: " +
						// leftRightResults[0] + " ResultRight: " +
						// leftRightResults[1]);
						record.calcRank(leftRightResults);
						if (rul != null) {
							// Log.d(TAG,
							// "DictionaryWorker:createMatchingRecords: notifyNewRecord");
							rul.notifyNewRecord(record);
						}
						resultNum++;
					}
					lineCount++;
					if (rul != null && (lineCount % 1000 == 0))
						rul.notifyProgress();
				}
				if (hasMessages(SEARCH_CANCEL)) {
					result = DONE_CANCELLED;

				}
			} catch (Exception e) {
				notifyError(rul, e.getLocalizedMessage());
				result = DONE_ERROR;
			}
			return result;
		}

		protected void notifyError(IRecordsUpdateListener rul, String msg) {
			if (rul != null)
				rul.notifyError(msg);
		}
	}

	public void searchNthRecord(int nth, IRecordsUpdateListener rul) {
		cancelSearchOperation();
		Message msg = Message.obtain(handler);
		msg.what = SEARCH_NTH_RECORD;
		msg.arg1 = nth;
		msg.obj = rul;

		handler.sendMessage(msg);
	}

	public void searchFirstNRecords(int n, IRecordsUpdateListener rul) {
		cancelSearchOperation();
		Message msg = Message.obtain(handler);
		msg.what = SEARCH_FIRSTN_RECORDS;
		msg.arg1 = n;
		msg.obj = rul;
		handler.sendMessage(msg);
	}

	public void searchMatchingRecords(String regex, int language,
			int matchFullWordMode, boolean amatch_enabled, int maxResults,
			IRecordsUpdateListener rul) {
		cancelSearchOperation();
		Bundle bundle = new Bundle();
		bundle.putString(REGEX_KEY, regex);
		bundle.putInt(MATCH_MODE_KEY, language);
		bundle.putInt(MATCH_FULL_WORD_KEY, matchFullWordMode);
		bundle.putInt(MAX_RESULTS, maxResults);
		bundle.putBoolean(AMATCH_ENABLED, amatch_enabled);
		Message msg = Message.obtain(handler);
		msg.what = SEARCH_MATCHING_RECORDS;
		msg.setData(bundle);
		msg.obj = rul;
		handler.sendMessage(msg);
	}

}
